爱客仕-前端团队博客园

Event Loop

前言

众所周知,Javascript是工作原理是单线程的,至于为什么是单线程,估计很多人都不曾去了解。
JavaScript作为浏览器脚本语言,其主要用途是与用户互动和操作DOM文档,这决定了它只能是单线程。
否则俩个线程同时操作一个DOM节点,到底听谁的呢?
虽然HTML5提出Web Worker新标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。
这个新标准并没有改变JavaScript单线程的本质。

任务队列

单线程指的是事件一个一个按照顺序执行,必须等前面的执行完了,下一个事件才能执行。这样是非常耗资源的。
所以Javascript分为同步任务(主线程上排队执行的任务)和异步任务(不进入主线程的任务列表,即task queue)。
只有等任务队列通知主线程某个异步任务可以执行了,这个任务才能执行。具体流程如下:

1.所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

2.主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。

3.一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

4.主线程不断重复上面的第三步。

事件和回调函数

“任务队列”是一个事件的队列(也可以理解成消息的队列),IO设备完成一项任务,就在”任务队列”中添加一个事
件,表示相关的异步任务可以进入”执行栈”了。主线程读取”任务队列”,就是读取里面有哪些事件。

“任务队列”中的事件,除了IO设备的事件以外,还包括一些用户产生的事件(比如鼠标点击、页面滚动等等)。只
要指定过回调函数,这些事件发生时就会进入”任务队列”,等待主线程读取。

所谓”回调函数”(callback),就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执
行异步任务,就是执行对应的回调函数。

“任务队列”是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,
只要执行栈一清空,”任务队列”上第一位的事件就自动进入主线程。但是,由于存在后文提到的”定时器”功能,主
线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。

Event Loop(事件循环机制)

既然Javascript是单线程,那么它又是如何解决日益复杂的网站js代码。靠的就是 Event Loop(事件循环机制)

主线程从”任务队列”中读取事件,这个过程是循环不断的,这种运行机制又称为Event Loop(事件循环)。

请看下图,可以更好地理解Event Loop。(转自Philip Roberts的演讲《Help, I’m stuck in an event-loop》)

上图中,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在”任务队列”中加入各种事件(click,load,done)。
只要栈中的代码执行完毕,主线程就会去读取”任务队列”,依次执行那些事件所对应的回调函数。
执行栈中的代码(同步任务),总是在读取”任务队列”(异步任务)之前执行。

用定时器的例子应该更容易理解点

1
2
setTimeout(function(){console.log(1);}, 0);
console.log(2);

上面代码的执行结果总是2,1,因为只有在执行完第二行以后,系统才会去执行”任务队列”中的回调函数。

参考

Philip Roberts的演讲Help, I’m stuck in an event-loop》演讲视频

Javascript是单线程的深入分析

阮一峰-JavaScript 运行机制详解:再谈Event Loop